/**********************************************************************************
  Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.

  (c) Copyright 1996 - 2002  Gary Henderson (gary.henderson@ntlworld.com),
                             Jerremy Koot (jkoot@snes9x.com)

  (c) Copyright 2002 - 2004  Matthew Kendora

  (c) Copyright 2002 - 2005  Peter Bortas (peter@bortas.org)

  (c) Copyright 2004 - 2005  Joel Yliluoma (http://iki.fi/bisqwit/)

  (c) Copyright 2001 - 2006  John Weidman (jweidman@slip.net)

  (c) Copyright 2002 - 2006  funkyass (funkyass@spam.shaw.ca),
                             Kris Bleakley (codeviolation@hotmail.com)

  (c) Copyright 2002 - 2007  Brad Jorsch (anomie@users.sourceforge.net),
                             Nach (n-a-c-h@users.sourceforge.net),
                             zones (kasumitokoduck@yahoo.com)

  (c) Copyright 2006 - 2007  nitsuja


  BS-X C emulator code
  (c) Copyright 2005 - 2006  Dreamer Nom,
                             zones

  C4 x86 assembler and some C emulation code
  (c) Copyright 2000 - 2003  _Demo_ (_demo_@zsnes.com),
                             Nach,
                             zsKnight (zsknight@zsnes.com)

  C4 C++ code
  (c) Copyright 2003 - 2006  Brad Jorsch,
                             Nach

  DSP-1 emulator code
  (c) Copyright 1998 - 2006  _Demo_,
                             Andreas Naive (andreasnaive@gmail.com)
                             Gary Henderson,
                             Ivar (ivar@snes9x.com),
                             John Weidman,
                             Kris Bleakley,
                             Matthew Kendora,
                             Nach,
                             neviksti (neviksti@hotmail.com)

  DSP-2 emulator code
  (c) Copyright 2003         John Weidman,
                             Kris Bleakley,
                             Lord Nightmare (lord_nightmare@users.sourceforge.net),
                             Matthew Kendora,
                             neviksti


  DSP-3 emulator code
  (c) Copyright 2003 - 2006  John Weidman,
                             Kris Bleakley,
                             Lancer,
                             z80 gaiden

  DSP-4 emulator code
  (c) Copyright 2004 - 2006  Dreamer Nom,
                             John Weidman,
                             Kris Bleakley,
                             Nach,
                             z80 gaiden

  OBC1 emulator code
  (c) Copyright 2001 - 2004  zsKnight,
                             pagefault (pagefault@zsnes.com),
                             Kris Bleakley,
                             Ported from x86 assembler to C by sanmaiwashi

  SPC7110 and RTC C++ emulator code
  (c) Copyright 2002         Matthew Kendora with research by
                             zsKnight,
                             John Weidman,
                             Dark Force

  S-DD1 C emulator code
  (c) Copyright 2003         Brad Jorsch with research by
                             Andreas Naive,
                             John Weidman

  S-RTC C emulator code
  (c) Copyright 2001-2006    byuu,
                             John Weidman

  ST010 C++ emulator code
  (c) Copyright 2003         Feather,
                             John Weidman,
                             Kris Bleakley,
                             Matthew Kendora

  Super FX x86 assembler emulator code
  (c) Copyright 1998 - 2003  _Demo_,
                             pagefault,
                             zsKnight,

  Super FX C emulator code
  (c) Copyright 1997 - 1999  Ivar,
                             Gary Henderson,
                             John Weidman

  Sound DSP emulator code is derived from SNEeSe and OpenSPC:
  (c) Copyright 1998 - 2003  Brad Martin
  (c) Copyright 1998 - 2006  Charles Bilyue'

  SH assembler code partly based on x86 assembler code
  (c) Copyright 2002 - 2004  Marcus Comstedt (marcus@mc.pp.se)

  2xSaI filter
  (c) Copyright 1999 - 2001  Derek Liauw Kie Fa

  HQ2x, HQ3x, HQ4x filters
  (c) Copyright 2003         Maxim Stepin (maxim@hiend3d.com)

  Win32 GUI code
  (c) Copyright 2003 - 2006  blip,
                             funkyass,
                             Matthew Kendora,
                             Nach,
                             nitsuja

  Mac OS GUI code
  (c) Copyright 1998 - 2001  John Stiles
  (c) Copyright 2001 - 2007  zones


  Specific ports contains the works of other authors. See headers in
  individual files.


  Snes9x homepage: http://www.snes9x.com

  Permission to use, copy, modify and/or distribute Snes9x in both binary
  and source form, for non-commercial purposes, is hereby granted without
  fee, providing that this license information and copyright notice appear
  with all copies and any derived work.

  This software is provided 'as-is', without any express or implied
  warranty. In no event shall the authors be held liable for any damages
  arising from the use of this software or it's derivatives.

  Snes9x is freeware for PERSONAL USE only. Commercial users should
  seek permission of the copyright holders first. Commercial use includes,
  but is not limited to, charging money for Snes9x or software derived from
  Snes9x, including Snes9x or derivatives in commercial game bundles, and/or
  using Snes9x as a promotion for your commercial product.

  The copyright holders request that bug fixes and improvements to the code
  should be forwarded to them so everyone can benefit from the modifications
  in future versions.

  Super NES and Super Nintendo Entertainment System are trademarks of
  Nintendo Co., Limited and its subsidiary companies.
**********************************************************************************/



#include <allegro.h>
#undef TRUE
#undef FALSE

#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include "swinder.h"
#include "sb.h"

#include "scancode.h"

#include <dpmi.h>
#include <go32.h>

#include "snes9x.h"
#include "memmap.h"
#include "debug.h"
#include "ppu.h"
#include "snapshot.h"
#include "gfx.h"
#include "display.h"
#include "apu.h"
#include "soundux.h"

#ifdef NETPLAY_SUPPORT
#include "netplay.h"
#endif

#define COUNT(a) (sizeof(a) / sizeof(a[0]))

#define sti() __asm__ __volatile__ ("sti": : :"memory")
#define cli() __asm__ __volatile__ ("cli": : :"memory")

static int screen_pitch = 0;
uint32 screen_width = 0;
uint32 screen_height = 0;
static int mode = -1;
static bool8 planar = FALSE;
static bool8 in_text_mode = TRUE;
static bool8 stretch = FALSE;
BITMAP *off_screen;
static BITMAP *sub_screen;
static uint8 *Delta = NULL;
static int num_sidewinders = 0;
static bool8 grip_initialised = FALSE;
extern int NumControllers;
static bool8 mouse_installed = FALSE;
static int prev_mouse_x = 0;
static int prev_mouse_y = 0;
static uint32 superscope_turbo = 0;
static uint32 superscope_pause = 0;
static int mouse_offset_x = 0;
static int mouse_offset_y = 0;
static double mouse_scale_h = 1.0;
static double mouse_scale_v = 1.0;
static bool8 wait_for_vsync = FALSE;
static bool8 interpolate = FALSE;

uint32 last_rendered_width = 0;
uint32 last_rendered_height = 0;

#ifdef GRIP_SUPPORT
#include "grip.h"
void InitGrip ();
void ReadGrip ();
#endif

#ifdef SIDEWINDER_SUPPORT
void InitSidewinders ();
int ReadSidewinders ();
extern bool8 alternate_sidewinder_mapping;
#endif

extern uint32 joypads [5];
extern BITMAP *screen;

void SaveScreenshot ();

#ifdef JOYSTICK_SUPPORT
extern bool8 joystick_has_four_buttons;
extern bool8 joystick_has_six_buttons;
extern bool8 joystick_has_eight_buttons;
#endif

#define ATTRCON_ADDR	0x3c0
#define MISC_ADDR	0x3c2
#define VGAENABLE_ADDR	0x3c3
#define SEQ_ADDR	0x3c4
#define GRACON_ADDR	0x3ce
#define CRTC_ADDR	0x3d4
#define STATUS_ADDR	0x3da

typedef struct
{
    unsigned port;
    unsigned char index;
    unsigned char value;
} Register;

typedef Register *RegisterPtr;
void outRegArray (Register *r, int n);

Register scr256x256[] =
{
     { 0x3c2, 0x00, 0xe3},{ 0x3d4, 0x00, 0x5f},{ 0x3d4, 0x01, 0x3f},
     { 0x3d4, 0x02, 0x40},{ 0x3d4, 0x03, 0x82},{ 0x3d4, 0x04, 0x4A},
     { 0x3d4, 0x05, 0x9A},{ 0x3d4, 0x06, 0x23},{ 0x3d4, 0x07, 0xb2},
     { 0x3d4, 0x08, 0x00},{ 0x3d4, 0x09, 0x61},{ 0x3d4, 0x10, 0x0a},
     { 0x3d4, 0x11, 0xac},{ 0x3d4, 0x12, 0xff},{ 0x3d4, 0x13, 0x20},
     { 0x3d4, 0x14, 0x40},{ 0x3d4, 0x15, 0x07},{ 0x3d4, 0x16, 0x1a},
     { 0x3d4, 0x17, 0xa3},{ 0x3c4, 0x01, 0x01},{ 0x3c4, 0x04, 0x0e},
     { 0x3ce, 0x05, 0x40},{ 0x3ce, 0x06, 0x05},{ 0x3c0, 0x10, 0x41},
     { 0x3c0, 0x13, 0x00}
};

typedef struct
{
    int width;
    int height;
    int mode;
} Mode;

static Mode modes [] = {
    {320, 240, GFX_MODEX},	    // 0
    {320, 200, GFX_VGA},	    // 1
    {256, 256, GFX_VGA},	    // 2
    {640, 480, GFX_AUTODETECT},	    // 3
    {640, 480, GFX_VESA1},	    // 4
    {640, 480, GFX_VESA2B},	    // 5
    {640, 480, GFX_VESA2L},	    // 6
    {640, 400, GFX_XTENDED},	    // 7
    {800, 600, GFX_AUTODETECT},	    // 8

    {320, 240, GFX_AUTODETECT},	    // 9
    {640, 480, GFX_AUTODETECT},	    // 10
    {512, 384, GFX_AUTODETECT},	    // 11
    {800, 600, GFX_AUTODETECT}	    // 12
};

void S9xGraphicsMode ()
{
    if (in_text_mode)
    {
	if (mode < 0)
	{
	    if (Settings.SixteenBit)
	    {
		if (Settings.SupportHiRes || interpolate)
		    mode = 10;
		else
		    mode = 9;
	    }
	    else
	    {
		if (Settings.SupportHiRes)
		    mode = 3;
		else
		    mode = 2;
	    }
	}

	int ret;
	do
	{
	    screen_width = modes [mode].width;
	    screen_height = modes [mode].height;

	    set_color_depth (Settings.SixteenBit ? 16: 8);
	    if (modes [mode].mode == GFX_VGA)
		ret = set_gfx_mode (modes [mode].mode, 320, 200, 0, 0);
	    else
		ret = set_gfx_mode (modes [mode].mode, modes [mode].width,
				    modes [mode].height, 0, 0);
	} while (ret < 0 && ++mode < COUNT(modes));

	planar = modes [mode].mode == GFX_MODEX;

	if (ret < 0)
	{
	    fprintf (stderr, "Unable to switch to requested screen mode/resolution:\n%s\n",
		    allegro_error);
	    S9xExit ();
	}

	if (modes [mode].mode == GFX_VGA && screen_width == 256)
	{
	    outRegArray (scr256x256, sizeof (scr256x256) / sizeof (Register));
	    screen->w = screen->cr = 256;
	    screen->h = screen->cb = 256;
	    for (int i = 1; i < 256; i++)
		screen->line[i] = screen->line[i - 1] + 256;
	}

	clear_to_color (screen, 0);

	if (install_keyboard ())
	{
	    set_gfx_mode (GFX_TEXT, 0, 0, 0, 0);
	    fprintf (stdout, "Keyboard initialisation failed.\n");
	    S9xExit ();
	}
	if (!install_mouse ())
	{
	    mouse_installed = TRUE;
	    set_mouse_range (0, 0, screen_width, screen_height);
	    position_mouse (screen_width / 2, screen_height / 2);
	    prev_mouse_x = mouse_x;
	    prev_mouse_y = mouse_y;
	}
	else
	    mouse_installed = FALSE;
	in_text_mode = FALSE;
    }
}

void S9xTextMode ()
{
    if (!in_text_mode)
    {
	set_color_depth (8);
	remove_keyboard ();
	remove_mouse ();
	set_gfx_mode (GFX_TEXT, 0, 0, 0, 0);
	in_text_mode = TRUE;
    }
}

void S9xInitDisplay (int argc, char **argv)
{
    if (allegro_init () < 0)
    {
	fprintf (stdout, "Unable to initialise Allegro library.\n");
	S9xExit ();
    }

    extern int i_love_bill;

    if (mode >= 9 || (mode < 0 && Settings.SixteenBit))
    {
	set_color_depth (16);
	Settings.Transparency = TRUE;
	Settings.SixteenBit = TRUE;
    }
    else
    {
	set_color_depth (8);
	Settings.Transparency = FALSE;
	Settings.SixteenBit = FALSE;
    }

    /*i_love_bill = FALSE;*/

    off_screen = create_bitmap (IMAGE_WIDTH + 2, IMAGE_HEIGHT);

    GFX.Screen = (uint8 *) off_screen->dat;
    if (!GFX.Screen)
    {
	fprintf (stdout, "Cannot allocate screen buffer.\n");
	S9xExit ();
    }
    GFX.Pitch = off_screen->w;
    if (Settings.SixteenBit)
    {
	GFX.Pitch *= 2;
	sub_screen = create_bitmap ((IMAGE_WIDTH + 2), IMAGE_HEIGHT);
	Delta = new uint8 [(IMAGE_WIDTH + 2) * IMAGE_HEIGHT * 2];
	if (!sub_screen || !Delta)
	{
	    fprintf (stdout, "Cannot allocate screen buffer.\n");
	    S9xExit ();
	}
	GFX.SubScreen = (uint8 *) sub_screen->dat;
	GFX.ZBuffer = (uint8 *) malloc (off_screen->w * IMAGE_HEIGHT);
	GFX.SubZBuffer = (uint8 *) malloc (off_screen->w * IMAGE_HEIGHT);
    }
    else
    {
	GFX.SubScreen = GFX.Screen;
	GFX.ZBuffer = (uint8 *) malloc (off_screen->w * IMAGE_HEIGHT);
	GFX.SubZBuffer = NULL;
	sub_screen = NULL;
	Delta = NULL;
    }
    screen_pitch = off_screen->w;
}

void S9xDeinitDisplay ()
{
    S9xTextMode ();
#ifdef GRIP_SUPPORT
    if (grip_initialised)
    {
	GrShutdown ();
	GrUnlink ();
    }
#endif
    destroy_bitmap (off_screen);
    if (sub_screen)
	destroy_bitmap (sub_screen);

    delete Delta;
    if (GFX.ZBuffer)
	free ((char *) GFX.ZBuffer);
    if (GFX.SubZBuffer)
	free ((char *) GFX.SubZBuffer);
    GFX.ZBuffer = NULL;
    GFX.SubZBuffer = NULL;
}

void S9xInitInputDevices ()
{
    if (Settings.JoystickEnabled)
    {
#ifdef GRIP_SUPPORT
	InitGrip ();
	if (grip_initialised)
	    num_sidewinders = 0;
	else
#endif

#ifdef SIDEWINDER_SUPPORT
	{
#ifdef GRIP_SUPPORT
	    usleep (20000);
#endif
	    InitSidewinders ();
	}
#else
	{ /* empty */ }
#endif

#ifdef JOYSTICK_SUPPORT
	if (joystick_has_four_buttons)
	    joy_type = JOY_TYPE_4BUTTON;
	else
	if (joystick_has_six_buttons)
	    joy_type = JOY_TYPE_6BUTTON;
	else
	if (joystick_has_eight_buttons)
	    joy_type = JOY_TYPE_8BUTTON;
	else
	    joy_type = JOY_TYPE_2PADS;
	if (num_sidewinders == 0 && !grip_initialised)
	    initialise_joystick ();
#endif
    }
}

#ifdef JOYSTICK_SUPPORT
void InitJoysticks ()
{
}

static int old_joy_left = 0;
static int old_joy_right = 0;
static int old_joy_up = 0;
static int old_joy_down = 0;
static int old_joy_b1 = 0;
static int old_joy_b2 = 0;
static int old_joy_b3 = 0;
static int old_joy_b4 = 0;
static int old_joy_b5 = 0;
static int old_joy_b6 = 0;
static int old_joy_b7 = 0;
static int old_joy_b8 = 0;
static int old_joy2_left = 0;
static int old_joy2_right = 0;
static int old_joy2_up = 0;
static int old_joy2_down = 0;
static int old_joy2_b1 = 0;
static int old_joy2_b2 = 0;

void ReadJoysticks ()
{
    if (num_sidewinders == 0 && !grip_initialised)
    {
	poll_joystick ();
	if (!joystick_has_four_buttons && !joystick_has_six_buttons &&
	    !joystick_has_eight_buttons)
	{
	    if (joy_b1 ^ old_joy_b1)
	    {
		if (joy_b1)
		    joypads [0] |= SNES_A_MASK;
		else
		    joypads [0] &= ~SNES_A_MASK;
	    }
	    if (joy_b2 ^ old_joy_b2)
	    {
		if (joy_b2)
		    joypads [0] |= SNES_B_MASK;
		else
		    joypads [0] &= ~SNES_B_MASK;
	    }
	}
	else
	{
	    if (joy_b1 ^ old_joy_b1)
	    {
		if (joy_b1)
		    joypads [0] |= SNES_Y_MASK;
		else
		    joypads [0] &= ~SNES_Y_MASK;
	    }
	    if (joy_b2 ^ old_joy_b2)
	    {
		if (joy_b2)
		    joypads [0] |= SNES_X_MASK;
		else
		    joypads [0] &= ~SNES_X_MASK;
	    }
	}
	if (joy_b3 ^ old_joy_b3)
	{
	    if (joy_b3)
		joypads [0] |= SNES_B_MASK;
	    else
		joypads [0] &= ~SNES_B_MASK;
	}
	if (joy_b4 ^ old_joy_b4)
	{
	    if (joy_b4)
		joypads [0] |= SNES_A_MASK;
	    else
		joypads [0] &= ~SNES_A_MASK;
	}
	if (joy_b5 ^ old_joy_b5)
	{
	    if (joy_b5)
		joypads [0] |= SNES_TL_MASK;
	    else
		joypads [0] &= ~SNES_TL_MASK;
	}
	if (joy_b6 ^ old_joy_b6)
	{
	    if (joy_b6)
		joypads [0] |= SNES_TR_MASK;
	    else
		joypads [0] &= ~SNES_TR_MASK;
	}
	if (joy_b7 ^ old_joy_b7)
	{
	    if (joy_b7)
		joypads [0] |= SNES_SELECT_MASK;
	    else
		joypads [0] &= ~SNES_SELECT_MASK;
	}
	if (joy_b8 ^ old_joy_b8)
	{
	    if (joy_b8)
		joypads [0] |= SNES_START_MASK;
	    else
		joypads [0] &= ~SNES_START_MASK;
	}
	if (joy_left ^ old_joy_left)
	{
	    if (joy_left)
		joypads [0] |= SNES_LEFT_MASK;
	    else
		joypads [0] &= ~SNES_LEFT_MASK;
	}
	if (joy_right ^ old_joy_right)
	{
	    if (joy_right)
		joypads [0] |= SNES_RIGHT_MASK;
	    else
		joypads [0] &= ~SNES_RIGHT_MASK;
	}
	if (joy_up ^ old_joy_up)
	{
	    if (joy_up)
		joypads [0] |= SNES_UP_MASK;
	    else
		joypads [0] &= ~SNES_UP_MASK;
	}
	if (joy_down ^ old_joy_down)
	{
	    if (joy_down)
		joypads [0] |= SNES_DOWN_MASK;
	    else
		joypads [0] &= ~SNES_DOWN_MASK;
	}

	if (!joystick_has_four_buttons && !joystick_has_six_buttons &&
	    !joystick_has_eight_buttons)
	{
	    if (joy2_b1 ^ old_joy2_b1)
	    {
		if (joy2_b1)
		    joypads [1] |= SNES_A_MASK;
		else
		    joypads [1] &= ~SNES_A_MASK;
	    }
	    if (joy2_b2 ^ old_joy2_b2)
	    {
		if (joy2_b2)
		    joypads [1] |= SNES_B_MASK;
		else
		    joypads [1] &= ~SNES_B_MASK;
	    }
	    if (joy2_left ^ old_joy2_left)
	    {
		if (joy2_left)
		    joypads [1] |= SNES_LEFT_MASK;
		else
		    joypads [1] &= ~SNES_LEFT_MASK;
	    }
	    if (joy2_right ^ old_joy2_right)
	    {
		if (joy2_right)
		    joypads [1] |= SNES_RIGHT_MASK;
		else
		    joypads [1] &= ~SNES_RIGHT_MASK;
	    }
	    if (joy2_up ^ old_joy2_up)
	    {
		if (joy2_up)
		    joypads [1] |= SNES_UP_MASK;
		else
		    joypads [1] &= ~SNES_UP_MASK;
	    }
	    if (joy2_down ^ old_joy2_down)
	    {
		if (joy2_down)
		    joypads [1] |= SNES_DOWN_MASK;
		else
		    joypads [1] &= ~SNES_DOWN_MASK;
	    }
	}
	old_joy_left = joy_left;
	old_joy_right = joy_right;
	old_joy_up = joy_up;
	old_joy_down = joy_down;
	old_joy_b1 = joy_b1;
	old_joy_b2 = joy_b2;
	old_joy_b3 = joy_b3;
	old_joy_b4 = joy_b4;
	old_joy_b5 = joy_b5;
	old_joy_b6 = joy_b6;
	old_joy_b7 = joy_b7;
	old_joy_b8 = joy_b8;
	old_joy2_left = joy2_left;
	old_joy2_right = joy2_right;
	old_joy2_up = joy2_up;
	old_joy2_down = joy2_down;
	old_joy2_b1 = joy2_b1;
	old_joy2_b2 = joy2_b2;
    }
}
#endif

void S9xSetPalette ()
{
    uint16 Brightness = IPPU.MaxBrightness * 138;
    PALLETE p;
    for (int i = 0; i < 256; i++)
    {
	p[i].r = (((PPU.CGDATA [i] >> 0) & 0x1F) * Brightness) >> 10;
	p[i].g = (((PPU.CGDATA [i] >> 5) & 0x1F) * Brightness) >> 10;
	p[i].b = (((PPU.CGDATA [i] >> 10) & 0x1F) * Brightness) >> 10;
    }
    set_palette_range (p, 0, 255, FALSE);
}

void S9xProcessEvents (bool8 block)
{
    static char prev_keystate[128];
    extern volatile char key[128];

#ifdef GRIP_SUPPORT
    ReadGrip ();
#endif

#ifdef SIDEWINDER_SUPPORT
    if (num_sidewinders)
	ReadSidewinders ();
#endif

    char key1[128];
    char *keystate = (char *) key1;
    int fn = 0;

    memcpy (key1, (char *) key, sizeof (key1));

#undef KEY_DOWN
#define KEY_DOWN(a) (keystate[a])
#undef KEY_PRESS
#define KEY_PRESS(a) (keystate[a] && !prev_keystate[a])
#undef KEY_WASPRESSED
#define KEY_WASPRESSED(a) (prev_keystate[a] && !keystate[a])
#undef PROCESS_KEY
#define PROCESS_KEY(k, b, v)\
if (KEY_PRESS(k)) b |= v;\
if (KEY_WASPRESSED(k)) b &= ~v;

    if (KEY_PRESS (SCANCODE_ESCAPE))
	S9xExit ();

    // Joypad 1:
    PROCESS_KEY(SCANCODE_K,		    joypads [0], SNES_RIGHT_MASK)
    PROCESS_KEY(SCANCODE_CURSORRIGHT,	    joypads [0], SNES_RIGHT_MASK)
    PROCESS_KEY(SCANCODE_H,		    joypads [0], SNES_LEFT_MASK)
    PROCESS_KEY(SCANCODE_CURSORLEFT,	    joypads [0], SNES_LEFT_MASK)
    PROCESS_KEY(SCANCODE_N,		    joypads [0], SNES_DOWN_MASK)
    PROCESS_KEY(SCANCODE_J,		    joypads [0], SNES_DOWN_MASK)
    PROCESS_KEY(SCANCODE_CURSORDOWN,	    joypads [0], SNES_DOWN_MASK)
    PROCESS_KEY(SCANCODE_U,		    joypads [0], SNES_UP_MASK)
    PROCESS_KEY(SCANCODE_CURSORUP,	    joypads [0], SNES_UP_MASK)
    PROCESS_KEY(SCANCODE_ENTER,		    joypads [0], SNES_START_MASK)
    PROCESS_KEY(SCANCODE_SPACE,		    joypads [0], SNES_SELECT_MASK)

    PROCESS_KEY(SCANCODE_A,		    joypads [0], SNES_TL_MASK)
    PROCESS_KEY(SCANCODE_V,		    joypads [0], SNES_TL_MASK)
    PROCESS_KEY(SCANCODE_Q,		    joypads [0], SNES_TL_MASK)

    PROCESS_KEY(SCANCODE_Z,		    joypads [0], SNES_TR_MASK)
    PROCESS_KEY(SCANCODE_B,		    joypads [0], SNES_TR_MASK)
    PROCESS_KEY(SCANCODE_W,		    joypads [0], SNES_TR_MASK)

    PROCESS_KEY(SCANCODE_S,		    joypads [0], SNES_X_MASK)
    PROCESS_KEY(SCANCODE_M,		    joypads [0], SNES_X_MASK)
    PROCESS_KEY(SCANCODE_E,		    joypads [0], SNES_X_MASK)

    PROCESS_KEY(SCANCODE_X,		    joypads [0], SNES_Y_MASK)
    PROCESS_KEY(SCANCODE_COMMA,		    joypads [0], SNES_Y_MASK)
    PROCESS_KEY(SCANCODE_R,		    joypads [0], SNES_Y_MASK)

    PROCESS_KEY(SCANCODE_D,		    joypads [0], SNES_A_MASK)
    PROCESS_KEY(SCANCODE_PERIOD,	    joypads [0], SNES_A_MASK)
    PROCESS_KEY(SCANCODE_T,		    joypads [0], SNES_A_MASK)

    PROCESS_KEY(SCANCODE_C,		    joypads [0], SNES_B_MASK)
    PROCESS_KEY(SCANCODE_SLASH,		    joypads [0], SNES_B_MASK)
    PROCESS_KEY(SCANCODE_Y,		    joypads [0], SNES_B_MASK)

    // Joypad 2:
//    PROCESS_KEY(SCANCODE_CURSORRIGHT,	    joypads [1], SNES_RIGHT_MASK)
//    PROCESS_KEY(SCANCODE_CURSORLEFT,	    joypads [1], SNES_LEFT_MASK)
//    PROCESS_KEY(SCANCODE_CURSORDOWN,	    joypads [1], SNES_DOWN_MASK)
//    PROCESS_KEY(SCANCODE_CURSORUP,	    joypads [1], SNES_UP_MASK)
    PROCESS_KEY(SCANCODE_KEYPADENTER,	    joypads [0], SNES_START_MASK)
    PROCESS_KEY(SCANCODE_KEYPADPLUS,	    joypads [0], SNES_SELECT_MASK)
    PROCESS_KEY(SCANCODE_INSERT,	    joypads [0], SNES_X_MASK)
    PROCESS_KEY(SCANCODE_REMOVE,	    joypads [0], SNES_Y_MASK)
    PROCESS_KEY(SCANCODE_HOME,		    joypads [0], SNES_A_MASK)
    PROCESS_KEY(SCANCODE_END,		    joypads [0], SNES_B_MASK)
    PROCESS_KEY(SCANCODE_PAGEUP,	    joypads [0], SNES_TL_MASK)
    PROCESS_KEY(SCANCODE_PAGEDOWN,	    joypads [0], SNES_TR_MASK)

    if (KEY_PRESS (SCANCODE_0))
	Settings.DisableHDMA = !Settings.DisableHDMA;
    if (KEY_PRESS (SCANCODE_1))
	Settings.BG_Forced ^= 1;
    if (KEY_PRESS (SCANCODE_2))
	Settings.BG_Forced ^= 2;
    if (KEY_PRESS (SCANCODE_3))
	Settings.BG_Forced ^= 4;
    if (KEY_PRESS (SCANCODE_4))
	Settings.BG_Forced ^= 8;
    if (KEY_PRESS (SCANCODE_5))
	Settings.BG_Forced ^= 16;
    if (KEY_PRESS (SCANCODE_6))
	Settings.SwapJoypads = !Settings.SwapJoypads;
    if (KEY_PRESS (SCANCODE_7))
    {
	if (IPPU.Controller == SNES_SUPERSCOPE)
	    show_mouse (NULL);
	S9xNextController ();
	if (IPPU.Controller == SNES_SUPERSCOPE)
	    show_mouse (screen);
    }
    if (KEY_PRESS (SCANCODE_8))
	Settings.BGLayering = !Settings.BGLayering;
    if (KEY_PRESS (SCANCODE_9))
	if (Settings.SixteenBit)
	    Settings.Transparency = !Settings.Transparency;

    if (KEY_PRESS(SCANCODE_TAB))
	superscope_turbo = !superscope_turbo;
    PROCESS_KEY(SCANCODE_GRAVE, superscope_pause, 1);

    if (KEY_PRESS(SCANCODE_F1))
	fn = 1;
    if (KEY_PRESS(SCANCODE_F2))
	fn = 2;
    if (KEY_PRESS(SCANCODE_F3))
	fn = 3;
    if (KEY_PRESS(SCANCODE_F4))
	fn = 4;
    if (KEY_PRESS(SCANCODE_F5))
	fn = 5;
    if (KEY_PRESS(SCANCODE_F6))
	fn = 6;
    if (KEY_PRESS(SCANCODE_F7))
	fn = 7;
    if (KEY_PRESS(SCANCODE_F8))
	fn = 8;
    if (KEY_PRESS(SCANCODE_F9))
	fn = 9;
    if (KEY_PRESS(SCANCODE_F10))
	fn = 10;
    if (KEY_PRESS(SCANCODE_F11))
	fn = 11;
    if (KEY_PRESS(SCANCODE_F12))
	fn = 12;

    if (fn > 0)
    {
	if (!KEY_DOWN(SCANCODE_LEFTALT) && !KEY_DOWN(SCANCODE_LEFTSHIFT))
	{
	    if (fn == 11)
	    {
		S9xLoadSnapshot (S9xChooseFilename (TRUE));
	    }
	    else if (fn == 12)
	    {
		Snapshot (S9xChooseFilename (FALSE));
	    }
	    else
	    {
		char def [PATH_MAX];
		char filename [PATH_MAX];
		char drive [_MAX_DRIVE];
		char dir [_MAX_DIR];
		char ext [_MAX_EXT];

		_splitpath (Memory.ROMFilename, drive, dir, def, ext);
		sprintf (filename, "%s%s%s.%03d",
			 S9xGetSnapshotDirectory (), SLASH_STR, def,
			 fn - 1);
		S9xLoadSnapshot (filename);
	    }
	}
	else if (KEY_DOWN(SCANCODE_LEFTALT))
	{
	    if (fn >= 4)
		S9xToggleSoundChannel (fn - 4);
#ifdef DEBUGGER
	    else if (fn == 1)
		CPU.Flags |= DEBUG_MODE_FLAG;
#endif
	    else if (fn == 2)
		S9xLoadSnapshot (S9xChooseFilename (TRUE));
	    else if (fn == 3)
		Snapshot (S9xChooseFilename (FALSE));
	}
	else
	{
	    char def [PATH_MAX];
	    char filename [PATH_MAX];
	    char drive [_MAX_DRIVE];
	    char dir [_MAX_DIR];
	    char ext [_MAX_EXT];

	    _splitpath (Memory.ROMFilename, drive, dir, def, ext);
	    sprintf (filename, "%s%s%s.%03d",
		     S9xGetSnapshotDirectory (), SLASH_STR, def,
		     fn - 1);
	    Snapshot (filename);
	}
    }

    if (KEY_PRESS (SCANCODE_BREAK) || KEY_PRESS (SCANCODE_BREAK_ALTERNATIVE) ||
	KEY_PRESS (SCANCODE_SCROLLLOCK))
	Settings.Paused ^= 1;

    if (KEY_PRESS (SCANCODE_PRINTSCREEN))
	SaveScreenshot ();

    if (KEY_PRESS (SCANCODE_MINUS))
    {
	if (Settings.SkipFrames <= 1)
	    Settings.SkipFrames = AUTO_FRAMERATE;
	else
	if (Settings.SkipFrames != AUTO_FRAMERATE)
	    Settings.SkipFrames--;
    }

    if (KEY_PRESS (SCANCODE_EQUAL))
    {
	if (Settings.SkipFrames == AUTO_FRAMERATE)
	    Settings.SkipFrames = 1;
	else
	if (Settings.SkipFrames < 10)
	    Settings.SkipFrames++;
    }

    memcpy (prev_keystate, keystate, sizeof (prev_keystate));

    if (block)
	__dpmi_yield ();
}

void S9xSetTitle (const char *title)
{
}

void S9xPutImage (int width, int height)
{
    //extern volatile char key[128];

    if (IPPU.Controller == SNES_SUPERSCOPE)
	scare_mouse ();

    if (wait_for_vsync)
    {
	vsync ();
	//while ((inportb (0x3da) & 8) && !key [SCANCODE_ESCAPE]);
	//while (!(inportb (0x3da) & 8) && !key [SCANCODE_ESCAPE]);
    }

    if (interpolate && Settings.SixteenBit && screen_width >= 512 &&
	height < 240)
    {
	void TVMode (uint8 *srcPtr, uint32 srcPitch,
		     uint8 *deltaPtr,
		     BITMAP *dstBitmap, int width, int height);

	if (width != last_rendered_width || height != last_rendered_height)
	    memset (Delta, 0xff, (IMAGE_WIDTH + 2) * IMAGE_HEIGHT * 2);
	TVMode (GFX.Screen, GFX.Pitch, Delta, screen, width, height);
    }
    else
    if (stretch)
    {
	stretch_blit (off_screen, screen, 0, 0, width, height,
		      0, 0, screen_width, screen_height);
	mouse_offset_x = 0;
	mouse_offset_y = 0;
	mouse_scale_h = screen_width / (double) width;
	mouse_scale_v = screen_height / (double) height;
    }
    else
    if (Settings.SupportHiRes)
    {
	if (screen_width < width || screen_height < height)
	{
	    stretch_blit (off_screen, screen, 0, 0, width, height,
			  0, 0, screen_width, screen_height);
	    mouse_offset_x = 0;
	    mouse_offset_y = 0;
	    mouse_scale_h = screen_width / (double) width;
	    mouse_scale_v = screen_height / (double) height;
	}
	else
	{
	    int h = height;
	    int w = width;

	    if (h < 240)
		h <<= 1;
	    if (w == 256)
		w = 512;
	    if (w > screen_width)
		w = screen_width;
	    if (h > screen_height)
		h = screen_height;
	    mouse_offset_x = (screen_width - w) >> 1;
	    mouse_offset_y = (screen_height - h) >> 1;
	    stretch_blit (off_screen, screen, 0, 0, width, height,
			  mouse_offset_x, mouse_offset_y, w, h);
	    mouse_scale_h = w / (double) width;
	    mouse_scale_v = h / (double) height;
	}
    }
    else
    {
	if (modes [mode].mode == GFX_VGA && screen_width == 256)
	{
	    int y_buff;
	    int y_start;
	    int y_end;
	    int x_start = (screen_width - width) >> 1;

	    mouse_offset_x = 0;
	    mouse_scale_h = 1.0;
	    mouse_scale_v = 1.0;
	    if (screen_height >= height)
	    {
		mouse_offset_y = y_start = (screen_height - height) >> 1;
		y_end = y_start + height;
		y_buff = 0;
	    }
	    else
	    {
		y_start = 0;
		y_end = screen_height;
		y_buff = (height - screen_height) >> 1;
	    }
	    uint8 *s = GFX.Screen + screen_pitch * y_buff;
	    uint8 *p = screen->line[0] + screen->w * y_start + x_start;
	    for (int y = y_start; y < y_end; y++, s += screen_pitch, p += screen->w)
		movedata (_my_ds(), (unsigned int) s,
			  (unsigned int) screen->seg, (unsigned int) p,
			  width);
	}
	else
	{
	    mouse_offset_x = (screen_width - width) >> 1;
	    mouse_offset_y = (screen_height - height) >> 1;
	    mouse_scale_h = 1.0;
	    mouse_scale_v = 1.0;
	    blit (off_screen, screen, 0, 0, mouse_offset_x, mouse_offset_y,
		  width, height);
	}
    }
    if (IPPU.Controller == SNES_SUPERSCOPE)
	unscare_mouse ();

    last_rendered_width = width;
    last_rendered_height = height;
}

const char *S9xSelectFilename (const char *def, const char *dir1,
			    const char *ext1, const char *title)
{
    static char path [PATH_MAX];
    char buffer [PATH_MAX];

    S9xTextMode ();
    printf ("\n%s (default: %s): ", title, def);
    fflush (stdout);
    if (fgets (buffer, sizeof (buffer) - 1, stdin))
    {
	char *p = buffer;
	while (isspace (*p) || *p == '\n')
	    p++;
	if (!*p)
	{
	    strcpy (buffer, def);
	    p = buffer;
	}

	char *q = strrchr (p, '\n');
	if (q)
	    *q = 0;
	char fname [PATH_MAX];
	char drive [_MAX_DRIVE];
	char dir [_MAX_DIR];
	char ext [_MAX_EXT];

	_splitpath (p, drive, dir, fname, ext);
	_makepath (path, drive, *dir ? dir : dir1, fname, *ext ? ext : ext1);
	S9xGraphicsMode ();
	return (path);
    }
    S9xGraphicsMode ();
    return (NULL);
}

void outReg(Register r)
{
    switch (r.port)
    {
	/* First handle special cases: */

	case ATTRCON_ADDR:
	    /* reset read/write flip-flop */
	    inportb (STATUS_ADDR);
	    /* ensure VGA output is enabled */
	    outportb (ATTRCON_ADDR, r.index | 0x20);
	    outportb (ATTRCON_ADDR, r.value);
	    break;

	case MISC_ADDR:
	case VGAENABLE_ADDR:
	    /*	directly to the port */
	    outportb (r.port, r.value);
	    break;

	case SEQ_ADDR:
	case GRACON_ADDR:
	case CRTC_ADDR:
	default:
	    /*	index to port			   */
	    outportb (r.port, r.index);
	    /*	value to port+1 		   */
	    outportb (r.port + 1, r.value);
	    break;
    }
}

/*
    readyVgaRegs() does the initialization to make the VGA ready to
    accept any combination of configuration register settings.

    This involves enabling writes to index 0 to 7 of the CRT controller
    (port 0x3d4), by clearing the most significant bit (bit 7) of index
    0x11.
*/

void readyVgaRegs (void)
{
    int v;

    outportb (0x3d4, 0x11);
    v = inportb (0x3d5) & 0x7f;
    outportb (0x3d4, 0x11);
    outportb (0x3d5, v);
}
/*
	outRegArray sets n registers according to the array pointed to by r.
	First, indexes 0-7 of the CRT controller are enabled for writing.
*/

void outRegArray (Register *r, int n)
{
    readyVgaRegs ();
    while (n--)
	outReg (*r++);
}

void S9xExtraUsage ()
{
    printf ("\
-m  num     Screen mode:\n\
            0 - 320x240 (modex, slower), 1 - 320x200 (faster but clipped)\n\
            2 - 256x256 (fastest but non-standard), 3-6 - 640x480, 7 - 640x400\n\
            8 - 800x600, 9 - 320x240 16 bit, 10 - 640x480 16 bit\n");
    printf ("\
-scale      Scale SNES screen to fit S-VGA screen\n");
}

void S9xParseDisplayArg (char **argv, int &ind, int)
{
    if ((strcmp (argv [ind], "-m") == 0 ||
	 strcasecmp (argv [ind], "-mode") == 0 ) && argv [ind + 1])
    {
	mode = atoi (argv [++ind]);
	if (mode >= sizeof (modes) / sizeof (modes [0]))
	    mode = 0;
    }
    else
    if (strcasecmp (argv [ind], "-scale") == 0 ||
	strcasecmp (argv [ind], "-sc") == 0)
	stretch = TRUE;
    else
    if (strcasecmp (argv [ind], "-w") == 0)
	wait_for_vsync = TRUE;
    else
    if (strcasecmp (argv [ind], "-y") == 0)
    {
	Settings.SixteenBit = TRUE;
	Settings.SupportHiRes = TRUE;
	Settings.Transparency = TRUE;
	interpolate = TRUE;
    }
    else
	S9xUsage ();
}

volatile uint32 FrameTimer = 0;

static void TimerInterrupt (...)
{
    FrameTimer++;
}
static END_OF_FUNCTION (TimerInterrupt);

void InitTimer ()
{
    install_timer ();
    LOCK_VARIABLE (FrameTimer);
    LOCK_FUNCTION (TimerInterrupt);
    if (install_int (TimerInterrupt, Settings.FrameTime / 1000) < 0)
    {
	printf ("Snes9X: Dynamic speed limiting not available\n");
	Settings.SkipFrames = 3;
    }
}

int S9xMinCommandLineArgs ()
{
    return (2);
}

#include "display.h"

static int Rates[8] =
{
    0, 8192, 11025, 16500, 22050, 29300, 36600, 44000
};

bool8 S9xOpenSoundDevice (int mode, bool8 stereo, int buffer_size)
{
    int playback_rate = Rates [mode & 7];
    int sixteen_bit = TRUE;
    int in_stereo = stereo;

    so.mute_sound = TRUE;

    if (sx_sb_init (&in_stereo, &sixteen_bit, &playback_rate,
		    &buffer_size) < 0)
    {
	return (FALSE);
    }
    so.stereo = in_stereo;
    so.sixteen_bit = sixteen_bit;
    so.playback_rate = playback_rate;
    so.buffer_size = buffer_size;
    so.encoded = FALSE;

    atexit (sx_sb_exit);

    /* Lock variables into memory */

    void S9xMixSamples_end();
    void S9xMixSamplesO_end();
    void MixStereo (int);
    void MixStereo_end();
    void MixMono (int);
    void MixMono_end();
    void S9xAPUSetEndOfSample (int, Channel *);
    void S9xAPUSetEndOfSample_end();
    void S9xAPUSetEndX (int);
    void S9xAPUSetEndX_end();
    void S9xSetEnvelopeRate_end();
    void S9xSetEnvRate (Channel *, int32, int32);
    void S9xSetEnvRate_end ();

    extern int Echo [24000];
    extern int DummyEchoBuffer [MAX_BUFFER_SIZE];
    extern int MixBuffer [MAX_BUFFER_SIZE];
    extern int EchoBuffer [MAX_BUFFER_SIZE];
    extern int FilterTaps [8];
    extern unsigned long FIRIndex;
    extern int Loop [16];

    LOCK_FUNCTION (S9xMixSamples);
    LOCK_FUNCTION (S9xMixSamplesO);
    LOCK_FUNCTION (MixStereo);
    LOCK_FUNCTION (MixMono);
    LOCK_FUNCTION (S9xAPUSetEndX);
    LOCK_FUNCTION (S9xAPUSetEndOfSample);
    LOCK_FUNCTION (S9xSetEnvelopeRate);
    LOCK_FUNCTION (S9xSetEnvRate);
    LOCK_VARIABLE (APU);
    LOCK_VARIABLE (IAPU);
    LOCK_VARIABLE (SoundData);
    LOCK_VARIABLE (so);
    LOCK_VARIABLE (Echo);
    LOCK_VARIABLE (DummyEchoBuffer);
    LOCK_VARIABLE (MixBuffer);
    LOCK_VARIABLE (EchoBuffer);
    LOCK_VARIABLE (FilterTaps);
    LOCK_VARIABLE (FIRIndex);
    LOCK_VARIABLE (Loop);

    _go32_dpmi_lock_data ((void *) IAPU.RAM, 64 * 1024);
    _go32_dpmi_lock_data ((void *) IAPU.ShadowRAM, 64 * 1024);
    _go32_dpmi_lock_data ((void *) IAPU.CachedSamples, 256 * 1024);
    printf ("Rate: %d, Buffer size: %d, 16-bit: %s, Stereo: %s, Encoded: %s\n",
	    so.playback_rate, so.buffer_size, so.sixteen_bit ? "yes" : "no",
	    so.stereo ? "yes" : "no", so.encoded ? "yes" : "no");

    so.mute_sound = FALSE;

    return (TRUE);
}

void S9xGenerateSound ()
{
}

bool8 S9xReadMousePosition (int which1, int &x, int &y, uint32 &buttons)
{
    if (which1 == 0)
    {
	buttons = mouse_b;
	prev_mouse_x += mouse_x - screen_width / 2;
	prev_mouse_y += mouse_y - screen_height / 2;
	position_mouse (screen_width / 2, screen_height / 2);
	x = prev_mouse_x;
	y = prev_mouse_y;
	return (TRUE);
    }
    return (FALSE);
}

bool8 S9xReadSuperScopePosition (int &x, int &y, uint32 &buttons)
{
    buttons = (mouse_b & 3) | (superscope_turbo << 2) |
	      (superscope_pause << 3);
    x = (int) ((mouse_x - mouse_offset_x) / mouse_scale_h);
    y = (int) ((mouse_y - mouse_offset_y) / mouse_scale_v);

    return (TRUE);
}

#ifdef SIDEWINDER_SUPPORT
#define INITIAL_TIMEOUT 200
#define FIRST_STROBE_TIMEOUT 160
#define MIN_FIRST_STOBE_LEN 32
#define TIMEOUT 30
#define INTER_SIDEWINDER_STROBE_LEN 16

#define MODE_A_STROBES 15
#define MODE_B_STROBES 5
#define MAX_STROBES_PER_SIDEWINDER MODE_A_STROBES

#define SW_PORT	    0x201

extern int SidewinderToSNES [];
extern int SidewinderToSNES2 [];
int ReadSidewinders ();

void InitSidewinders ()
{
    num_sidewinders = ReadSidewinders ();
}

int ReadSidewinders ()
{
    static SW_READ_DATA prev_ret = {0};

    int j, i;
    unsigned char b;
    char *t;
    int strobes = 0;
    int start;
    unsigned char data [SW_MAX_SIDEWINDERS * MAX_STROBES_PER_SIDEWINDER * 2];
    unsigned char *p = data;
    unsigned char *pend = p + sizeof (data);
    struct SW_READ_DATA ret;
    unsigned int buttons;
    unsigned int parity;

    for (i = 0; i < SW_MAX_SIDEWINDERS; i++)
    {
	ret.buttons [i] = 0;
	ret.valid [i] = 0;
    }

    ret.count = 0;
    j = 0;

    cli ();
    outportb ((uint16) SW_PORT, (uint8) 0xff);
    b = inportb ((uint16) SW_PORT);
    b = inportb ((uint16) SW_PORT);

    if (!(b & 0x10))
    {
	/* Search for bit 4 going from 0 to 1 */
	do
	{
	    b = inportb ((uint16) SW_PORT);
	    if (++j >= INITIAL_TIMEOUT)
		goto exit;
	} while (!(b & 0x10));
    }

    j = 0;
    do
    {
	b = inportb ((uint16) SW_PORT);
	if (++j > FIRST_STROBE_TIMEOUT)
	    goto exit;
    } while (b & 0x10);

    do
    {
	j = 0;
	do
	{
	    b = inportb ((uint16) SW_PORT);
	    if (++j > TIMEOUT)
	    {
		*p = j;
		*(p + 1) = b;
		p += 2;
		goto exit;
	    }
	} while (!(b & 0x10));
	*p = j;
	*(p + 1) = b;
	p += 2;

	j = 0;
	do
	{
	    b = inportb ((uint16) SW_PORT);
	    if (++j > TIMEOUT)
		goto exit;
	} while (b & 0x10);
    } while (p < pend);

exit:
    sti ();
    start = 0;
    strobes = 0;
    for (i = 0; i < p - data; i += 2)
    {
	if (data [i] >= INTER_SIDEWINDER_STROBE_LEN)
	{
	    if (strobes == MODE_A_STROBES || strobes == MODE_B_STROBES)
	    {
		buttons = 0;
		if (strobes == MODE_A_STROBES)
		{
		    for (j = 0; j < MODE_A_STROBES; j++)
			buttons |= (((data [start + 1 + (j << 1)] >> 5) ^ 1) & 1) << j;
		}
		else
		{
		    for (j = 0; j < MODE_B_STROBES; j++)
			buttons |= (((data [start + 1 + (j << 1)] >> 5) ^ 7) & 7) << (j * 3);
		}
		parity = 0;
		for (j = 0; j < SW_NUM_SIDEWINDER_BUTTONS; j++)
		    if (buttons & (1 << j))
			parity++;

		if ((parity & 1) != ((buttons >> SW_NUM_SIDEWINDER_BUTTONS) & 1))
		{
		    /* No partity error, update the joypad button state */
		    ret.valid [ret.count] = 1;
		    ret.buttons [ret.count++] = buttons & ~SW_PARITY_MASK;
		}
		else
		    ret.valid [ret.count++] = 0;
	    }
	    else
		ret.valid [ret.count++] = 0;
	    start = i;
	    strobes = 1;
	}
	else
	    strobes++;
    }
    if (ret.count)
    {
	if (ret.count < 2)
	    NumControllers = 2;
	else
	{
	    if (ret.count > NumControllers)
	    {
		NumControllers = ret.count;
		if (NumControllers > 5)
		    NumControllers = 5;
	    }
	}

	for (int s = 0; s < ret.count && s < 5; s++)
	{
	    if (ret.valid [s])
	    {
		for (int i = 0; i < SW_NUM_SIDEWINDER_BUTTONS; i++)
		{
		    if ((prev_ret.buttons [s] ^ ret.buttons [s]) & (1 << i))
		    {
			if (alternate_sidewinder_mapping)
			{
			    if (ret.buttons [s] & (1 << i))
				joypads [s] |= SidewinderToSNES2 [i];
			    else
				joypads [s] &= ~SidewinderToSNES2 [i];
			}
			else
			{
			    if (ret.buttons [s] & (1 << i))
				joypads [s] |= SidewinderToSNES [i];
			    else
				joypads [s] &= ~SidewinderToSNES [i];
			}
		    }
		}
		prev_ret.buttons [s] = ret.buttons [s];
	    }
	}
    }
    return (ret.count);
}
#endif

#ifdef GRIP_SUPPORT
void InitGrip ()
{
    char buffer [_MAX_PATH];
    FILE *fs = NULL;
    const char *grip_dir = getenv ("GRIP");
    uint8 *grip_library;
    int size;

    if (grip_dir)
    {
	strcpy (buffer, grip_dir);
	strcat (buffer, "\\");
	strcat (buffer, "GRIP.GLL");
	fs = fopen (buffer, "rb");
    }

    if (!fs)
	fs = fopen ("GRIP.GLL", "rb");

    if (!fs)
	fs = fopen ("C:\\GRIP\\GRIP.GLL", "rb");

    if (!fs)
	fs = fopen ("C:\\GRAVIS\\GRIP\\GRIP.GLL", "rb");

    if (fs)
    {
	fseek (fs, 0, SEEK_END);
	size = ftell (fs);
	if (size > 32 * 1024)
	{
	    fclose (fs);
	    return;
	}
	grip_library = new uint8 [size];
	fseek (fs, 0, SEEK_SET);
	if (fread (grip_library, 1, size, fs) != size)
	{
	    delete grip_library;
	    fclose (fs);
	    return;
	}
	fclose (fs);
	if (!GrLink (grip_library, size))
	{
	    delete grip_library;
	    return;
	}
	delete grip_library;
	if (!GrInitialize ())
	{
	    GrUnlink ();
	    return;
	}
	grip_initialised = TRUE;
	printf ("Grip Initialised\n");
    }
}

struct GripData
{
    GRIP_BITFIELD buttons;
    GRIP_BITFIELD direction;
};

static uint32 GamePadProToSNES [10] = {
    SNES_TR_MASK, SNES_TL_MASK, SNES_X_MASK, SNES_A_MASK,
    SNES_Y_MASK, SNES_TL_MASK, SNES_B_MASK, SNES_TR_MASK,
    SNES_START_MASK, SNES_SELECT_MASK
};

static uint32 GamePadProToSNESDir [4] = {
    SNES_RIGHT_MASK, SNES_LEFT_MASK, SNES_UP_MASK, SNES_DOWN_MASK
};

static struct GripData grip_prev_data [5] = { {0, 0} };

void ReadGrip ()
{
    if (grip_initialised)
    {
	GrRefresh (0);
	int count = 0;
	int slot;
	GRIP_BITFIELD connected = GrGetSlotMap ();
	for (slot = 1; slot < 5; slot++)
	{
	    if (connected & (1 << slot))
	    {
		char product [17];
		product [0] = 0;
		GrGetProductName (slot, product);
		if (strcasecmp (product, "GamePad Pro") == 0)
		{
		    GRIP_BITFIELD buttons;
		    buttons = GrGetPackedValues (slot, GRIP_CLASS_BUTTON, 0, 9);
		    int x = GrGetValue (slot, GRIP_CLASS_AXIS, 0);
		    int y = GrGetValue (slot, GRIP_CLASS_AXIS, 1);
		    GRIP_BITFIELD direction = 0;

		    if (x > 1)
			direction |= 1;
		    else
		    if (x < 1)
			direction |= 2;
		    if (y > 1)
			direction |= 4;
		    else
		    if (y < 1)
			direction |= 8;

		    int i;
		    for (i = 0; i < 10; i++)
		    {
			if ((grip_prev_data [slot].buttons ^ buttons) & (1 << i))
			{
			    if (buttons & (1 << i))
				joypads [count] |= GamePadProToSNES [i];
			    else
				joypads [count] &= ~GamePadProToSNES [i];
			}
		    }
		    for (i = 0; i < 4; i++)
		    {
			if ((grip_prev_data [slot].direction ^ direction) & (1 << i))
			{
			    if (direction & (1 << i))
				joypads [count] |= GamePadProToSNESDir [i];
			    else
				joypads [count] &= ~GamePadProToSNESDir [i];
			}
		    }

		    grip_prev_data [slot].buttons = buttons;
		    grip_prev_data [slot].direction = direction;
		}
		else
		{
		}
		count++;
	    }
	    if (count < 2)
		NumControllers = 2;
	    else
	    if (count > NumControllers)
		NumControllers = count;
	}
    }
}
#endif

void _makepath (char *path, const char *drive, const char *dir,
		const char *fname, const char *ext)
{
    if (drive && *drive)
    {
	*path = *drive;
	*(path + 1) = ':';
	*(path + 2) = 0;
    }
    else
	*path = 0;

    if (dir && *dir)
    {
	strcat (path, dir);
	if (strlen (dir) != 1 || *dir != '\\')
	    strcat (path, SLASH_STR);
    }

    strcat (path, fname);
    if (ext && *ext)
    {
        strcat (path, ".");
        strcat (path, ext);
    }
}

void _splitpath (const char *path, char *drive, char *dir, char *fname,
		 char *ext)
{
    if (*path && *(path + 1) == ':')
    {
	*drive = toupper (*path);
	path += 2;
    }
    else
	*drive = 0;

    char *slash = strrchr (path, SLASH_CHAR);
    if (!slash)
	slash = strrchr (path, '/');
    char *dot = strrchr (path, '.');
    if (dot && slash && dot < slash)
	dot = NULL;

    if (!slash)
    {
	if (*drive)
	    strcpy (dir, "\\");
	else
	    strcpy (dir, "");
	strcpy (fname, path);
        if (dot)
        {
	    *(fname + (dot - path)) = 0;
	    strcpy (ext, dot + 1);
        }
	else
	    strcpy (ext, "");
    }
    else
    {
	if (*drive && *path != '\\')
	{
	    strcpy (dir, "\\");
	    strcat (dir, path);
	    *(dir + (slash - path) + 1) = 0;
	}
	else
	{
	    strcpy (dir, path);
	    if (slash - path == 0)
		*(dir + 1) = 0;
	    else
		*(dir + (slash - path)) = 0;
	}

	strcpy (fname, slash + 1);
        if (dot)
	{
	    *(fname + (dot - slash) - 1) = 0;
    	    strcpy (ext, dot + 1);
	}
	else
	    strcpy (ext, "");
    }
}

extern volatile uint32 FrameTimer;

void S9xSyncSpeed ()
{
#ifdef NETPLAY_SUPPORT
    if (Settings.NetPlay)
    {
	// XXX: Send joypad position update to server
	// XXX: Wait for heart beat from server
	S9xNetPlaySendJoypadUpdate (joypads [0]);
	if (!S9xNetPlayCheckForHeartBeat ())
	{
	    do
	    {
		CHECK_SOUND ();
		S9xProcessEvents (FALSE);
		__dpmi_yield ();
	    } while (!S9xNetPlayCheckForHeartBeat ());
	    IPPU.RenderThisFrame = TRUE;
	    IPPU.SkippedFrames = 0;
	}
	else
	{
	    if (IPPU.SkippedFrames < 10)
	    {
		IPPU.SkippedFrames++;
		IPPU.RenderThisFrame = FALSE;
	    }
	    else
	    {
		IPPU.RenderThisFrame = TRUE;
		IPPU.SkippedFrames = 0;
	    }
	}
    }
    else
#endif
    if (Settings.SkipFrames == AUTO_FRAMERATE)
    {
	while (FrameTimer == 0)
	{
	    S9xProcessEvents (FALSE);
	    __dpmi_yield ();
	}

	if (FrameTimer > 10)
	    FrameTimer = 10;
	if (FrameTimer > 1 && IPPU.SkippedFrames < 10)
	{
	    IPPU.SkippedFrames++;
	    IPPU.RenderThisFrame = FALSE;
	}
	else
	{
	    IPPU.RenderThisFrame = TRUE;
	    IPPU.SkippedFrames = 0;
	}
	FrameTimer--;
    }
    else
    {
	if (++IPPU.FrameSkip >= Settings.SkipFrames)
	{
	    IPPU.FrameSkip = 0;
	    IPPU.RenderThisFrame = TRUE;
	    IPPU.SkippedFrames = 0;
	}
	else
	{
	    IPPU.SkippedFrames++;
	    IPPU.RenderThisFrame = FALSE;
	}
    }
}

void S9xMessage (int type, int message_no, const char *str)
{
    switch (type)
    {
    case S9X_ERROR:
    case S9X_FATAL_ERROR:
	fprintf (stderr, "%s\n", str);
	return;
    case S9X_TRACE:
    case S9X_DEBUG:
	printf ("%s\n", str);
	break;

    default:
	switch (message_no)
	{
	case S9X_ROM_INFO:
	case S9X_HEADERS_INFO:
	case S9X_ROM_CONFUSING_FORMAT_INFO:
	case S9X_ROM_INTERLEAVED_INFO:
	case S9X_USAGE:
	    fprintf (stderr, "%s\n", str);
	    return;
	}
    }
}

